Next | Prev | Up | Top | Contents | Index

Checking the Progress of Asynchronous Requests

You can test the progress and completion of an asynchronous operation by polling. Your program can be informed of the completion of an operation in a variety of ways. All of the methods discussed here are demonstrated in the example program that starts in "Asynchronous I/O Example".


Polling for Status

You can check the progress of any asynchronous operation (including aio_fsync()) using aio_error(). As long as the operation is incomplete, this function returns EIINPROGRESS. When the operation is complete, you can check the final return code from read(), write(), or fsync() using aio_return() (see the aio_error(3) and aio_return(3) reference pages).

To see in an example of polling for status, see function inWait0() under "Asynchronous I/O Example". This function is used when the aiocb is initialized with SIGEV_NONE, meaning that no notification is to be returned at the completion of the operation. The function waits for an asynchronous operation to complete using a loop in the general form shown in Example 8-2.

Example 8-2 : Polling for Asynchronous Completion

int waitForEndOfAsyncOp(aiocb *pab)
{
    while (EINPROGRESS == (ret = aio_error(pab)))
        sginap(0);
    return ret;
}
The function result is the final return code from the read, write, or sync operation that was started. Under the Frame Scheduler, the call to sginap() would be replaced with a call to frs_yield().


Checking for Completion

In the aiocb, the program can specify one of three things to be done when the operation is complete:

In addition, the aio_suspend() function blocks its caller until one of a list of pending operations is complete (see the aio_suspend(3) reference page).

These choices give you a wide variety of design options. Your program can

Most of these methods are demonstrated in the program starting in "Asynchronous I/O Example".

Tip: When operating under the Frame Scheduler, a handler or callback function can simply set a flag. An activity process can test the flag in each minor frame, calling frs_yield() immediately if the flag is not set.


Establishing a Completion Signal

You request a signal from an asynchronous operation by setting these values in the aiocb (refer to /usr/include/aio.h and /usr/include/sys/signal.h):

aio_sigevent.sigev_notifySet to SIGEV_SIGNAL.
aio_sigevent.sigev_signoThe number of the signal. This should be one of the POSIX real-time signal numbers (see "Signals").
aio_sigevent.sigev_valueA value to be passed to the signal handler. This can be used to inform the signal handler of which I/O operation has completed; for example, it could be the address of the aiocb.

When you set up a signal handler for asynchronous completion, do so using sigaction() and specify the SA_SIGINFO flag (see the sigaction(2) reference page). This has two benefits: any new completion signal that arrives while the first is being handled is queued; and the aio_sigev.sigev_value word is passed to the handler in a siginfo structure.


Establishing a Callback Function

You request a callback at the end of an asynchronous operation by setting the following values in the aiocb:

aio_sigevent.sigev_notifySet to SIGEV_CALLBACK.
aio_sigevent.sigev_funcThe address of the callback function. Its prototype must be void functionName(union sigval);
aio_sigevent.sigev_valueA word to be passed to the callback function. This can be used to inform the function of which I/O operation has completed; for example, it could be the address of the aiocb.

The callback function is invoked from the asynchronous process when the read(), write() or fsync() operation finishes. This notification method has the lowest overhead and shortest latency, but it requires careful design to avoid race conditions in the use of shared variables.

The asynchronous processes are created with sproc(), so they share the address space of the process that initialized asynchronous I/O. They typically execute in a different CPU from the real-time processes using that address space. Since the callback function could be entered at any time, it must coordinate its use of shared data structures. This is a good place to use a lock (see "Locks"). Locks have very low overhead in cases such as this, where there is likely to be little contention for the use of the lock.

Tip: You can call aio_read() or aio_write() from within a callback function or within a signal handler. This lets you start another operation with the least delay. The code in Example 8-3 demonstrates a hypothetical set of subroutines to schedule asynchronous reads and writes using a single aiocb. The principle functions and global variables it uses are:

pendingIOAn array of records, each holding one request for an I/O operation.
dontTouchThatStuffA lock used to gain exclusive use of pendingIO.
scheduleRead()A function that accepts a request to read some amount of data, from a specified file descriptor, at a specified file offset. It places the request in pendingIO and then, if no asynchronous operation is under way, initiates it.
yeahWeFinishedOne()The callback function that is entered when an asynchronous operation completes. If any more operations are pending, it initiates one.
initiatePending()A function that initiates one selected pending operation. It prepares the aiocb structure, including the specification of yeahWeFinishedOne() as the callback function. The lock dontTouchThatStuff must be held before this function is called.

Note: The code in Example 8-3 is not intended to be realistic and is not recommended as a model. In order to demonstrate the use of callback functions and the aiocb, it essentially duplicates work that could be done by the lio_listio() feature of asynchronous I/O.

Example 8-3 : Set of Functions to Schedule Asynchronous I/O

#define _ABI_SOURCE
#include <signal.h>
#include <aio.h>
#include <ulocks.h>
#define MAX_PENDING 10
#define STATUS_EMPTY 0
#define STATUS_ACTIVE 1
#define STATUS_PENDING 2
static struct onePendingIO {
    int status;
    int theFile;
    void *theData;
    off_t theSize;
    off_t theSeek;
    int readNotWrite;
    } pendingIO[MAX_PENDING];
static unsigned numPending;
static struct aiocb theAiocb;
static ulock_t dontTouchThatStuff;
static unsigned scanner;
static void initiatePending(int P);
static void
yeahWeFinishedOne(union sigval S)
{
    ussetlock(dontTouchThatStuff);
    pendingIO[S.sival_int].status = STATUS_EMPTY;
    if (numPending)
    {
        while (pendingIO[scanner].status != STATUS_PENDING)
        {
            if (++scanner >= MAX_PENDING)
                scanner = 0;
        }
        initiatePending(scanner);
    }
    usunsetlock(dontTouchThatStuff);
}
static void
initiatePending(int P) /* lock must be held on entry */
{
    theAiocb.aio_fildes = pendingIO[P].theFile;
    theAiocb.aio_buf = pendingIO[P].theData;
    theAiocb.aio_nbytes = pendingIO[P].theSize;
    theAiocb.aio_offset = pendingIO[P].theSeek;
    theAiocb.aio_sigevent.sigev_notify = SIGEV_CALLBACK;
    theAiocb.aio_sigevent.sigev_func = yeahWeFinishedOne;
    theAiocb.aio_sigevent.sigev_value.sival_int = P;
    if (pendingIO[P].readNotWrite)
        aio_read(&theAiocb);
    else
        aio_write(&theAiocb);
    pendingIO[P].status = STATUS_ACTIVE;
    --numPending;
}
/*public*/ int 
scheduleRead( int FD, void *pdata, off_t len, off_t pos )
{
    int j;
    if (numPending >= MAX_PENDING)
        likeTotallyFreakOut();
    ussetlock(dontTouchThatStuff);
    for(j=0; pendingIO[j].status != STATUS_EMPTY; ++j)
        ;
    pendingIO[j].theFile = FD;
    pendingIO[j].theData = pdata;
    pendingIO[j].theSize = len;
    pendingIO[j].theSeek = pos;
    pendingIO[j].readNotWrite = 1;
    pendingIO[j].status = STATUS_PENDING;
    if (1 == ++numPending)
        initiatePending(j);
    usunsetlock(dontTouchThatStuff);
}

Holding Callbacks Temporarily

You can temporarily prevent callback functions from being entered using the aio_hold() function. This function is not defined in the POSIX standard; it is added by the MIPS ABI standard. Use it as follows:


Next | Prev | Up | Top | Contents | Index